{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6.11 学习率调节器\n",
    "\n",
    "本章的前面几个小节我们详细介绍了从基础的梯度下降法，它的各种变体，到动量法及各种子子孙孙，再到Adam算法的发展演进过程。在随机梯度下降法中，我们提到过动态调整学习率是帮助模型快速收敛的有效办法，因为它是各类优化算法中的关键参数。本节咱们就专注于这个话题，介绍更多知识。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.11.1 影响学习率主要因素\n",
    "\n",
    "先来看看调整学习率时，需要考虑哪些因素：\n",
    "\n",
    "1. 模型的复杂度：较复杂的模型可能需要较小的学习率，因为复杂的模型更难拟合数据。\n",
    "2. 训练数据的规模：较大的训练数据集可以设置稍大的学习率，因为大的训练数据集可以提供更多的信息，模型也更容易拟合数据。\n",
    "3. 训练过程中损失函数的变化情况：如果损失函数的变化较小，可能需要调高学习率；如果损失函数的变化较大不稳定，就需要适当调低学习率。\n",
    "4. 目标任务的复杂度：较复杂的目标任务可能需要较小的学习率，因为复杂的任务更难拟合。\n",
    "5. 优化器的类型：不同的优化器可能对学习率的敏感度不同，需要根据实际情况进行一定调整。\n",
    "6. Batch size：较小的batch size可能会带来较大的随机噪声，导致训练不稳定，一般设置为较小的学习率。\n",
    "\n",
    "所谓“牵一发而动全身”，这样来形容学习率在深度学习优化算法中的作用一点也不为过。事实上，除了上述因素之外，还有许多其他的因素可能会影响调整学习率的效果，在实战中需要根据具体情况进行调整。这么复杂而关键的问题，光靠人工手动调整太复杂，因此科学家们希望这件事能够自动的进行，因此就涌现出了“学习率调节器”（Learning Rate Scheduler）。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.11.2 学习率调节器\n",
    "\n",
    "学习率调节器是在训练神经网络时用来控制学习率的方法，更好的模型更新步骤大小的值。当学习率过大时，模型会振荡或者发散，而当学习率过小时，模型的收敛速度会变慢。学习率调节器的作用就是帮助我们在训练过程中动态调整学习率，以便获得更好的模型训练效果。\n",
    "\n",
    "学习率调节器的发展可以追溯到 20 世纪 60 年代，当时已经有人提出了调节学习率的方法。但是，直到 2012 年，学习率调节器才真正开始流行。这是因为在 2012 年，Geoffrey Hinton 和他的团队发表了著名的论文 \"ImageNet Classification with Deep Convolutional Neural Networks\"，提出了一种名为 \"AdaGrad\" 的学习率调节器。在此之后，学习率调节器发展迅速，出现了许多新的方法，包括 \"RMSProp\"、\"Adam\" 和 \"SGD with Warm Restarts\" 等。这些方法在实践中表现出了良好的性能，因此被广泛使用。现在，学习率调节器已经成为训练神经网络的重要工具，并且在机器学习界广受好评。\n",
    "\n",
    "目前常用的深度学习框架，如 PyTorch、TensorFlow、 Keras 等，都提供了内置的学习率调节器或者允许用户自定义学习率调节器。因此，在使用这些框架训练深度学习模型时，你可以直接使用这些框架提供的学习率调节器，也可以自己实现一个学习率调节器并使用。\n",
    "\n",
    "我们来看看一些常见的学习率调节器及其代码实现。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 学习率衰减（Learning Rate Decay）\n",
    "\n",
    "在训练过程中，每训练一定次数就将学习率降低一定比例。例如，每训练 100 次就将学习率降低为原来的一半。代码实现："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=100, gamma=0.5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 指数衰减法（Exponential Decay）\n",
    "\n",
    "每次迭代时将学习率乘上一个衰减率，从而使学习率逐渐降低。公式为：$$lr = lr_{0} * decay \\underline{} rate ^ {global \\underline{} step} $$  其中 $lr_{0}$ 是初始学习率，$decay \\underline{} rate$ 是衰减率，$global \\underline{} step$ 是当前迭代的次数，注意这里衰减率最好不要设置的太小。代码实现："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scheduler = torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma=0.95)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 余弦学习率调节（Cosine Annealing）\n",
    "\n",
    "根据余弦函数来调节学习率的方法，使得学习率在训练过程中先快速降低，然后慢慢升高。公式为：$$lr_{t} = lr_{min} + \\frac{lr_{max} - lr_{min}}{2} \\cdot (1 + \\cos(\\frac{T_{cur}}{T_{max}} \\cdot \\pi))$$ 其中 $lr_{t}$ 是当前时刻的学习率，$lr_{min}$ 和 $lr_{max}$ 分别是学习率的最小值和最大值，$T_{cur}$ 是当前迭代的次数，$T_{max}$ 是最大迭代次数。代码实现："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=100, eta_min=0.00001)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 自定义学习率调节\n",
    "\n",
    "通过一个lambda函数实现自定义的学习率调节器。例如模拟前面指数衰减法的代码实现："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=lambda epoch: 0.95 ** epoch)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "除了上述学习率调节器外，还有很多其他机制可以用于优化学习率的调整，比如预热（Warmup）。\n",
    "\n",
    "#### 预热\n",
    "\n",
    "预热（warmup）是指在训练神经网络模型时，将学习率慢慢从较小的值逐渐提升到较大的值的过程。这样做的目的是避免在训练开始时出现梯度爆炸或消失的情况，从而避免网络权值更新过快导致的收敛问题，使模型的训练更加稳定。公式为：$lr_{t} = lr_{min} + \\frac{lr_{max} - lr_{min}}{steps} \\cdot t$，其中 $lr_{t}$ 是当前时刻的学习率，$lr_{min}$ 和 $lr_{max}$ 分别是学习率的最小值和最大值，$steps$ 是预热的迭代次数，$t$ 是当前迭代的次数。代码实现："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=lambda t: min(t / warmup_steps, 0.001))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "注意，预热通常是在训练过程中的前几个 epoch 使用，并在达到一定的迭代次数后切换到其他的学习率调节方法。预热和学习率调节器是两个不同的概念，但是它们都可以用来调节学习率。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**梗直哥提示：这部分的完整代码实现涉及到比较多的对pytorch框架的讲解，以及实战经验。限于篇幅，欢迎选修进阶课程《梗直哥深度学习必修课：python实战》**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "[Next 7-1 卷积层](../Chapter-07%20卷积神经网络/7-1%20卷积层.ipynb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
